Моніторинг віртуальної пам`яті в ОС Linux

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

РОЗРАХУНКОВО-ПОЯСНЮВАЛЬНА ЗАПИСКА
до курсової роботи на тему:
"Моніторинг віртуальної пам'яті в ОС Linux"

Введення

Часто буває необхідно відстежити за роботою того чи іншого процесу з пам'яттю, наприклад, щоб виявити витоку пам'яті, дізнатися, в які моменти і скільки пам'яті процес виділяє. Для вирішення даного завдання існує ряд засобів, а саме:
· Файлова система / proc - дозволяє прочитати різну інформацію про всі системи в цілому та про кожного з процесів, в тому числі інформацію про використання процесом пам'яті та про відображеннях пам'яті даного процесу. (Приклад:
# Cat / proc / `pgrep test` / status
Name: test1
...
VmPeak: 1556 kB
VmSize: 1544 kB
VmLck: 0 kB
VmHWM: 308 kB
VmRSS: 308 kB
VmData: 148 kB
VmStk: 88 kB
VmExe: 4 kB
VmLib: 1276 kB
VmPTE: 12 kB
# Cat / proc / `pgrep test` / maps
08048000-08049000 r-xp 00000000 8:01 17432879 / home/twee/work/mstu/coding/memmon/test/test1
08049000-0804a000 rw-p 00000000 8:01 17432879 / home/twee/work/mstu/coding/memmon/test/test1
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7e4b000-b7e4c000 rw-p b7e4b000 00:00 0
b7e4c000-b7f75000 r-xp 00000000 3:05 1604119 / lib/tls/libc-2.3.6.so
b7f75000-b7f76000 r-p 00128000 3:05 1604119 / lib/tls/libc-2.3.6.so
b7f76000-b7f79000 rw-p 00129000 3:05 1604119 / lib/tls/libc-2.3.6.so
b7f79000-b7f7c000 rw-p b7f79000 00:00 0
b7f9d000-b7fb3000 r-xp 00000000 3:05 752968 / lib/ld-2.3.6.so
b7fb3000-b7fb5000 rw-p 00015000 3:05 752968 / lib/ld-2.3.6.so
bfc2a000-bfc40000 rw-p bfc2a000 00:00 0 [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
). На наведеній роздруківці видно, скільки пам'яті використовує процес test і під які саме потреби, а так само його карту пам'яті. Проте таким чином не можна відстежити динаміку роботи процесу з пам'яттю.
· Strace - утиліта, що дозволяє трасувати всі системні виклики, що виконуються даним процесом (зокрема, виділення пам'яті викликами brk / mmap). Вона використовує стандартний налагоджувальний механізм ядра під назвою ptrace - підключається до досліджуваного процесу як відладчик (викликів ptrace (), вказуючи при цьому прапор PTRACE_SYSCALL, що змушує систему повідомляти трасуючий процес про всіх системних викликах трассируемого). Приклад його роботи:
execve (»./test3», [«test3»], [/ * 61 vars * /]) = 0
...
fsync (0) = -1 EINVAL (Invalid argument)
mmap2 (NULL, 2101248, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) = 0xb7bf4000
fsync (1) = -1 EINVAL (Invalid argument)
fsync (2) = -1 EINVAL (Invalid argument)
munmap (0xb7bf4000, 2101248) = 0
exit_group (0)
На наведеній трасуванні видно, як процес виділяє і звільняє 2101248 байт пам'яті. На жаль, цей засіб не дозволяє стежити за всіма процесами в системі в цілому, а так само за виділеннями фізичних сторінок процесу.
Таким чином, виникає необхідність у засобі, що дозволяє відслідковувати не тільки виділення віртуальної пам'яті процесу, але і виділення окремих сторінок фізичної пам'яті в результаті сторінкових збоїв.


1. Аналітичний розділ

1.1 Постановка завдання

Розробити драйвер під Linux, що відслідковує виділення і звільнення процесами віртуальної пам'яті і виділення фізичних сторінок при сторінкових відмовах.
Драйвер повинен підтримувати динамічну завантаження і вивантаження без перезапуску системи та завдання списку процесів, за якими необхідно виконувати моніторинг.
Взаємодія з користувацької програмою здійснюється за допомогою файлів, створюваних у файловій системі / proc.
Ядро відповідає за створення та знищення процесів і за їх зв'язок із зовнішнім світом (введення і виведення). Взаємодія процесів один з одним (за допомогою сигналів, програмних каналів або інших засобів між процесами взаємодії) є основою загальної системної функціональності, і теж здійснюється за допомогою ядра. Планувальник, що розподіляє час центрального процесора між процесами, є частиною підсистеми управління процесами. У загальних словах, підсистема управління процесами реалізує абстракцію безлічі процесів, що працюють одночасно на одному або декількох процесорах.

1.1.1 Управління пам'яттю

Пам'ять комп'ютера - один з головних ресурсів, і продуктивність системи критично залежить від політики розподілу пам'яті. Ядро створює віртуальний адресний простір для кожного процесу, використовуючи при цьому обмежена кількість фізичної пам'яті і, при необхідності, вторинну пам'ять, таку, як жорсткий диск. У міру необхідності сторінки можуть бути вивантажені в файл підкачки, або файл, з якого вони були відображені в пам'ять (у разі, якщо вони не були модифіковані з моменту завантаження з файлу, вони просто видаляються з пам'яті). За замовчуванням ядро ​​не дозволяє виділити одному процесу більше пам'яті, ніж сумарний обсяг доступної оперативної та swap-пам'яті. Проте є така можливість, як overcommit («перевиделеніе»), яка дозволяє виділити набагато більше пам'яті, за умови, що реально іспользоватьcя буде лише невелика її частина (допустимо, при роботі з розрідженим масивом). Overcommit включається командою
# Echo 1> / proc / sys / vm / overcommit_memory
а відключається
# Echo 0> / proc / sys / vm / overcommit_memory
Цифра 1 означає вибраний режим управління перевиделеніем (0 означає його відсутність, 1 - припустимо перевиделеніе необмежених обсягів пам'яті, 2 - деякий евристичний алгоритм визначення максимально допустимого обсягу перевиделенія).

1.1.2 Файлова система

Система Linux заснована на концепції файлової системи. Практично будь-який об'єкт системи може бути розглянуто як файл. Ядро будує єдину ієрархічну файлову систему (єдине дерево директорій) на основі не володіє ієрархічною структурою обладнання. У результаті абстракція файлу активно використовується всією системою.

1.1.3 Підсистема введення-виведення

Практично будь-яка операція в системі є, по суті, операція з пристроєм. За винятком процесора, пам'яті і дуже невеликого числа інших елементів, порядок виконання роботи з пристроєм, а отже і виконуваний код, що виконує цю роботу, залежить головним чином від конкретного обладнання. Такий код називається драйвером пристрою. Ядро має включати в себе драйвери для всіх периферійних пристроїв, що входять в систему, від жорсткого диска до клавіатури.

1.1.4 Мережева підсистема

Ядро має керувати роботою з мережею, оскільки більшість мережевих операцій не залежить від процесу. Надходять мережеві пакети є асинхронними подіями, тобто під час роботи одного процесу може прийти пакет, адресований іншому процесу. Пакети повинні бути прийняті, розпізнані й розподілені до того, як про них дізнається процес. Система відповідає за передачу даних через програмні та мережеві інтерфейси, а так само управляти виконанням програм відповідно до роботи мережі. До того ж, всі завдання маршрутизації і виділення мережевих адрес виконує ядро.

1.1.5 Системні виклики

Системні виклики, такі як open (), fork (), read (), etc є сполучною інтерфейсом між ядром і користувацькими додатками. У Linux 2.6 існує близько 330 різних викликів (багато з них надлишкові або збережені за причинами сумісності). Їх виклик відбувається через переривання 0x80 або інструкцію sysenter (на сучасних процесорах). При цьому в регістр EAX поміщається номер системного виклику, а в інші 6 регістрів (крім ESP) - аргументи (тобто будь-який системний виклик може приймати до шести 32-бітових аргументів) у порядку EBX, ECX, EDX, ESI, EDI, EBP. Точка входу всіх системних викликів розташована у файлі arch/i386/kernel/entry.S, який викликає обробник конкретного виклику за таблицею викликів sys_call_table, передаючи їй регістри через стек.

1.1.6 Завантажувані модулі

Одна з важливих особливостей ядра Linux - це здатність розширювати власну функціональність безпосередньо в період виконання.
Кожен фрагмент виконуваного коду, який може бути доданий в ядро ​​під час його роботи, називається модулем ядра. Кожен модуль створюється з об'єктного коду, не пов'язаного в повноцінний виконуваний файл. Модуль може бути завантажений в ядро з допомогою програми insmod (що викликає функції create _ module () / init _ module ()), і вивантажений з допомогою rmmod (що викликає delete _ module ()). У даній роботі реалізує саме такий динамічно завантажуваний модуль.

1.1.7 Типи пристроїв

У Linux розрізняють три основних типи пристроїв. Кожен драйвер зазвичай відповідає одному з цих типів. Виділяють:
· Символьні драйвери
· Блокові драйвери
· Мережеві драйвери
Символьне пристрій може розглядатися як потік байт (так само як і файл); символьний драйвер повинен реалізовувати таку поведінку. Такий драйвер має, як правило, функції відкриття, закриття, читання та запису. Текстова консоль і послідовний порт - приклади символьних пристроїв. Вони можуть легко бути представлені абстракцією потоків. Робота з символьними пристроями здійснюється через спеціальні файли пристроїв, що знаходяться в директорії / dev. Єдине значуще відміну звичайного файлу від такого пристрою - це довільний доступ, тоді як до більшості символьних пристроїв можна звертатися лише послідовно.
Як і до символьним, доступ до блокових пристроїв можна отримати через файли в директорії / dev. Блоковий пристрій - це пристрій (наприклад, диск), здатний утримувати в собі файлову систему. У системі Unix блоковий пристрій може лише передавати один або більше цілих блоків даних, зазвичай по 512 байт. Інтерфейс взаємодії блокових драйверів з ядром значно відрізняється від інтерфейсу символьних драйверів.
Будь-яка мережева транзакція виконується через інтерфейс сокетів. Мережевий інтерфейс відповідає за передачу та прийом пакетів під управлінням мережевої підсистеми ядра незалежно від того, до яких саме транзакціях вони відносяться. Багато мережеві з'єднання (особливо використовують протокол TCP) орієнтовані на потоки даних. Але мережеві пристрої зазвичай працюють з пакетами, а не з потоками. Таким чином, мережеві пристрої містять в собі риси як символьних, так і блокових.

2. Конструкторський розділ

2.1 Модульна структура драйвера

Драйвер memmon складається з наступних модулів:
mmon.c - основний модуль, що відповідає за ініціалізацію і вивантаження драйвера
mm-fault.c - обробник сторінкових помилок
syscalls.c - головна частина перехоплення системних викликів
syscalls-entry.S - низькорівнева частина перехоплення системних викликів
watch-pids.c - список процесів, за якими здійснюється моніторинг, додавання і видалення з нього
events.c - кільцевий буфер подій

2.2 Ініціалізація та вивантаження драйвера

Ініціалізацію драйвера виконує функція int __init init (void). Вона викликається при завантаженні драйвера в контексті процесу, визвашего init_module () (системний виклик завантаження драйвера) і виконує наступні дії:
1. Ініціалізує бітову карту відслідковуються процесів і кільцевий буфер подій
2. Встановлює обробники системних викликів і сторінкової помилки
3. Створює директорію / proc / memmon і файли в ній
Створення файлів відбувається в останню чергу для того, щоб користувальницькі додатки не могли звернутися до драйвера до завершення ініціалізації.
Вивантаження виконує функція void __exit exit (void), викликається в контексті процесу, зробив delete_module (). Вона виконує дії, зворотні до init ():
1. Видаляє директорію / proc / memmon
2. Знімає перехоплення системних викликів і сторінкового відмови
3. Звільняє пам'ять

2.3 Взаємодія з одними додатками

Для взаємодії з одними додатками драйвер використовує файлову систему procfs - псевдо-файлову систему, що надає різну інформацію про систему. Будь-який драйвер може додавати в її ієрархію свої файли і папки для передачі користувацьким програмам різної інформації. Ядро надає ряд функцій для роботи з procfs, з який даний драйвер використовує наступні:
proc_mkdir () - створює папку в / proc
create_proc_entry () - створює файл у зазначеній папці / proc або самої / proc
remove_proc_entry () - видаляє папку або файл в / proc
На рівні ядра Linux будь-який відкритий файл представлений структурою file, що зберігає покажчик f_fops на структуру file_operations, що містить адреси обробників різних запитів до файлу. Припустимо, коли додаток відкриває файл (роблячи системний виклик open ()), він викликає узагальнений рівень файлових систем VFS (функцію vfs_read ()), яка в свою чергу викликає обробник f_ops-> open. Для звичайних файлових систем обробники запитів до файлу знаходяться в драйвері файлової системи, якій належить цей файл. Однако / proc не представляє з себе будь-якої реальної ФС на реальному сховище даних, і кожен драйвер, який додає туди файли, повинен для них надавати свої обробники (точки входу), які і будуть викликатися при роботі з цими файлами.
Файли, що створюються в / proc, представляються структурою proc_entry, що містить поле proc_fops, куди і заноситься покажчик на структуру file_operations для даного файлу.
Даний драйвер створює в папці / proc / memmon 2 файли - watch-pids - для додавання / видалення процесів в список відслідковуються і events - файл, що містить власне лог подій.

2.4 Перехоплення системних викликів

Одне із основних дій, виконуваних драйвером - перехоплення системних викликів.
Це досить небезпечний прийом, так як якщо 2 драйвера перехоплять один і той самий виклик, і будуть вивантажені не в тому порядку, в якому завантажилися, останній відновить неправильну адресу в таблиці викликів, внаслідок чого відбудеться збій при наступній спробі зробити даний виклик. У зв'язку з цим, починаючи з версії 2.5, ядро ​​більш не експортує таблицю системних викликів. Тим не менш, ця проблема усувається невеликим виправленням ядра (patch), який додає в довільний файл ядра наступні рядки, що експортують з ядра цієї таблиці.
extern void * sys_call_table [];
EXPORT_SYMBOL_GPL (sys_call_table);
Для перехоплення системних викликів є 3 таблиці покажчиків - оригінальних (системних) обробників, наших пре-обробників (викликаються ДО оригінального, та приймаючих аргументи) і пост-обробників (викликаються ПІСЛЯ і приймають значення, що повертається):
void * old_sys_call [NR_syscalls];
void * sys_call_trap [NR_syscalls];
void * sys_call_exit [NR_syscalls];
Крім того, є загальна таблиця структур, кожен елемент якої описує один з перехоплюваних системних викликів:
struct syscall_handler
{
/ * Syscall nr * /
int nr;
/ * Pre-call & post-call handler * /
void * hand1, * hand2;
};
Функція capture_syscalls (), що викликається при ініціалізації драйвера, копіює ці адреси в попередні 2 таблиці і записує в sys_call_table за потрібними номерами адресу універсального перехоплювача - syscalls_entry, що знаходиться у файлі syscalls-entry.
Необхідність у ассемблерной коді обумовлена ​​механізмом обробки системних викликів в Linux. На рис. 3 показаний стек на момент виклику обробника системного виклику (заміненого або стандартного). Проблема полягає в тому, що деякі стандартні обробники вимагають, щоб стік мав саме такий вигляд, і якщо викликати їх з нового обробника, вони правильно працювати не будуть. У зв'язку з цим syscalls_entry спочатку викликає пре-обробник системного виклику, потім замінює в стеку адресу повернення на адресу наступної інструкції і робить перехід на стандартний обробник, який отримує кадр стека в первісному вигляді. Потім, при поверненні, ми потрапляємо на наступну інструкцію, яка викликає пост-обробник і робить перход на початковий адресу повернення, на код в arch/i386/kernel/entry.S (точки входу всіх системних викликів в Linux). Ця адреса зберігається внизу стека ядра, там же де зберігається покажчик на поточне завдання і деяка інша службова інформація ядра. Дані дії продемонстровані на рис. 3.3-3.5.
Даний драйвер перехоплює такі системні виклики:
m [un] map () - виділення / звільнення регіону пам'яті
mremap () - переміщення регіону пам'яті
brk () - розширення / звуження сегмента даних програми
m [un] lock [all] () - блокування набору сторінок в робочому безлічі процесу
fsync () - використовується в якості маркера в журналі подій
Для кожного з викликів в журнал друкується ім'я виклику, PID викликав процесу і список (з розшифровкою, там, де це має сенс) аргументів.

2.5 Кільцевій буфер подій

Для зберігання журналу подій, таких як виділення блоків віртуальної пам'яті і сторінок, використовує кільцевої буфер, захищений спін-блокуванням. Розмір даного буфера може задаватися при завантаженні драйвера в якості його параметра, значення за замовчуванням - 32 кб, мінімальне - 8 кб. Пам'ять для буфера виділяється функцією kzalloc () - аналогом фукнції malloc / calloc () зі стандартної бібліотеки С. Рухаючись їй параметр GFP_KERNEL означає, що пам'ять виділяється для ядра (тобто не може бути пізніше вивантажено з диска), але не в атомарному контексті (поточний процес може бути відкладений до звільнення необхідної пам'яті).
Кожен запис в буфері представляє із себе наступну структуру:
enum memmon_event_type - тип події
{
NOTUSED = 0
MMAP2,
MUNMAP,
MREMAP,
MLOCK,
MUNLOCK,
MLOCKALL,
MUNLOCKALL,
BRK,
FSYNC,
ANON_PF, - сторінкова помилка на анонімній сторінці
SWAP_PF, - на сторінці з файлу підкачки
FILE_PF, - з розділяється файлу
SYSCALLRET - повернення з системного виклику
};
struct memmon_event
{
enum memmon_event_type type; - тип події
pid_t pid; - PID викликав процесу
union - специфічні для події дані
{
struct
{
void __user * start;
size_t len;
} Munmap;
struct
{
void __user * start;
size_t len;
unsigned long prot, flags;
unsigned long fd, off;
} Mmap2;
... ...
};
}
З буфером подій пов'язані такі змінні:
static struct memmon_event * events; - власне буфер
static int ev_start; - індекс найстарішою записи в буфері
static int ev_end; - індекс останнього запису
static int ev_ovf = 0; - чи було вже переповнення буферу
DECLARE_WAIT_QUEUE_HEAD (ev_waitq); - черга очікування (для блокуючого читання)
spinlock_t ev_lock = SPIN_LOCK_UNLOCKED; - спін-блокування для захисту від гонок при зверненні до буфера
Користувальницькі додатки запитують вміст буфера подій, читаючи файл / proc / memmon / events. Якщо при відкритті файлу не був встановлений прапор O_NONBLOCK, операція читання по ньому блокуюча - тобто, якщо нових даних в буфері немає, read () переводить процес в стан очікування (interruptible sleep) викликом функції wait_event_interruptible () до отримання сигналу або появи нових даних у буфері.
Крім open () і release (), що викликаються при відкритті (створення нової структури file) і її знищення, в file_operations даного файлу визначені всього 2 точки входу - read () і poll (). Оброблювач poll () визиваемается, коли якийсь процес робить виклик select () по даному файлу - очікує, поки на ньому будуть доступні для читання дані. Крім того, в прапорах структури file викликом nonseekable_open () скидається біт, що дозволяє робити виклик llseek () по файлу (оскільки дана операція позбавлена ​​сенсу для кільцевого буфера).
Для реалізації функції read () використовується абстракція під назвою seq_file, призначена для буферизації зчитувальних даних. Вона вимагає завдання 4 функцій - seq_start (), що викликається при початку читання з файлу, seq_next (), що викликається перед копіюванням в буфер користувача запису про чергове подію, seq_show (), власне осуществляющющей це копіювання, і seq_stop (), що викликається при завершенні передачі даних в користувальницький буфер (коли скопійовано затребованное кількість даних або не залишилося подій в буфері).
Рис. показує зв'язок між цими функціями та структурами.
При додаванні нової події в буфер відбувається пробудження всі очікують на черзі процесів викликом wake_up_interruptible_sync () (sync означає, що поточний процес не буде витіснений до розблокування всіх процесів).

2.6 Набір відслідковуються процесів

Набір відслідковуються процесів представляється у вигляді бітової карти на 65536 записів. При запуску нового процесу ядро ​​дає йому PID, на 1 більший, ніж PID попереднього процесу. При досягненні черговим процесом PID, рівного 65536, новим процесам PID виділяється з одиниці (тобто з першого позитивного незайнятого). Лише коли кількість процесів у системі перевищує цю цифру, ядро ​​починає виділяти більші номери. Дана ситуація вкрай малоймовірна і драйвером не обробляється.
Для додавання і видалення PID'ов відслідковуються процесів створюється файл / proc / memmon / watch-pids з обробниками open (), release (), read (), write ().
Оброблювач open (), у разі, якщо файл відкритий для читання, виділяє буфер ядра і роздруковує туди вміст поточної бітової карти (за допомогою функції bitmap_scnlistprintf ()). Спроба відкрити файл одночасно на читання і запис призводить до помилки EINVAL.
Оброблювач read () зчитує запитаний користувачем блок даних з цього буфера
Оброблювач write () зчитує одне число (можливо, із знаком) з користувальницького буфера. Якщо воно позитивне, воно сприймається як PID процесу, який необхідно додати в бітову карту. Якщо негативне - відповідний PID видаляється. Якщо 0 - бітова карта обнуляється (не відстежується жоден процес).

2.7 Перехоплення сторінкових помилок і виділень сторінкових фреймів

Можливо здійснити перехоплення сторінкових відмов, підмінивши зсув обробника виключення в IDT, однак цей метод вимагає повторення того ж аналізу збійного адреси, який робить і стандартний системний обробник, (файл mm / memory.c) що призвело б до падіння продуктивності системи. У зв'язку з цим було вирішено внести ще одну модифікацію в ядро. При обробці сторінкового відмови ядро спочатку визначає, чи відноситься збійна сторінка до віртуальної пам'яті процесу (якщо немає, йому надсилається сигнал SIGSEGV), після чого викликає функцію handle_pte_fault (). Та аналізує причину відмови і або довантажує сторінку з своп-файлу / файлу відображення, або виділяє процесу нову сторінку, або посилає йому SIGSEGV (наприклад, при спробі запису в регіон пам'яті, доступною тільки для читання), або відбувається ситуація OOM (out of memory ), в результаті якої зіпсований процес знищується з метою звільнення пам'яті. Модифікація ядра додає глобальну змінну-вказівник на зовнішню функцію, яку щоразу викликає handle_pte_fault. При ініціалізації драйвер заносить туди адресу своєї функції mm_handle_fault (), яка, в залежності від причини сторінкового відмови, заносить в буфер одне з трьох подій (разом з адресою та типом доступу - читання або запис):
ANON_PF - збій при зверненні до нової (ще не виділеної з фізичної пам'яті) сторінки;
SWAP_PF - збій при зверненні до сторінки, яка знаходиться у файлі підкачки;
FILE_PF - збій при зверненні до сторінки, яка може бути завантажена з файлу, відображеного в пам'ять (наприклад, код вашої бібліотеки).
Алгоритм аналізу причини сторінкового відмови продемонтсрірован на рис. 3.7.

2.8 Синхронізація

Так як доступ до кільцевого буферу відбувається з різних процесів, необхідно якийсь засіб запобігання від «перегонів» (race conditions). Ядро Linux надає цілий набір примітивів синхронізації. Однак, оскільки блокування виконується в тому числі і в атомарному контексті, тобто коли поточний процес не може бути переведений в режим очікування (наприклад, при приміщенні події з обробника переривання), єдиний підходящий примітив - спін-блокування (spin-lock), тобто проста бінарна змінна (зі значенням 0 або 1) і активним очікуванням на неї. Захоплення спін-блокування відбувається при початку операції читання буфера подій і перед додаванням в буфер нового події. Звільнення - по завершенні читання і додавання події, а так само перед перекладом поточного процесу в режим очікуванні в разі, коли нових даних в буфері немає.

3. Технологічний розділ

3.1 Мова і засоби програмування

Ядро Linux написано на мові С з невеликою кількістю коду на асемблері, і його система збирання, взагалі кажучи, підтримує тільки дані мови для використання в модулях. Вибір з них був зроблений на користь З через значно більшою структурованості написаного на ньому коду. Однак, невеликий обсяг коду драйвера довелося написати на асемблері, в силу чого він не є платформо-незалежною і прив'язаний до архітектури x86.
Для збірки драйвера використовується збиральна система ядра make / Kbuild і компілятор С з gcc (GNU Compiler Collection). Особливістю ядра Linux є відсутність сумісності на бінарному рівні, сумісність присутній лише на рівні вихідних текстів, внаслідок чого всі модулі і саме ядро ​​повинні бути зібрані однієї і тієї ж версією одного і того ж компілятора. Саме ядро ​​спочатку написано для компіляції за допомогою gcc - основного компілятора С в GNU-системах, тому він же повинен використовуватися і при компіляції модулів для нього.

3.2 Компіляція драйвера

Спочатку необхідно застосувати виправлення до ядра і перекомпілювати його. Це робиться командами:
# Cd / usr / src / linux
# Patch - p0 <memmon.patch
# Make
# Make install INSTALL_PATH = / boot modules_install
# Lilo
# Reboot
Після цього можлива сама збірка драйвера. Для цього з папки з його исходниками треба виконати команду $ make.

3.3 Робота з драйвером

Для завантаження драйвера необхідно виконати команду
# Insmod procmon.ko
Можна вказати розмір буфера подій:
# Insmod procmon.ko buflen = 65536
Додати процес з PID = 3000 до відстежувати:
$ Echo 3000> / proc / memmon / watch-pids
Видалити його:
$ Echo -3000> / proc / memmon / watch-pids
Додати процес з ім'ям test:
$ Echo `pgrep test`> / proc / memmon / watch-pids
Видалити всі процеси:
$ Echo 0> / proc / memmon / watch-pids
Переглянути буфер подій:
$ Cat / proc / memmon / events
Вивантаження драйвера робиться командою
# Rmmod procmon.ko
(Можливо, тільки якщо файли драйвера ніким не відкриті)


4. Експериментальний розділ

З метою вивчення особливостей виділення пам'яті в Linux був проведений ряд експериментів з виділенням пам'яті, її читанням / записом і звільненням.
1) запитувати невелика кількість (3-4) сторінок пам'яті, звертаємося до всієї пам'яті в цьому діапазоні, потім звільняємо її.
Журнал подій:
29305: anon page @ b7ee1fa0 (R)
29305: fsync (0)
29305: anon page @ b7f22d4e (R) - сторінкові відмови в сегменті коду libc.so
29305: anon page @ b7ee2000 (R)
29305: anon page @ b7e85180 (R)
29305: anon page @ b7e86330 (R)
29305: anon page @ b7f1f680 (R)
29305: anon page @ b7ef6470 (R)
29305: anon page @ b7e83140 (R)
29305: anon page @ b7e82370 (R)
29305: anon page @ b7e87de0 (R)
29305: brk (00000000)
29305: brk -> 134 520 832 (0804a000)
29305: brk (0806e000)
29305: brk -> 134 668 288 (0806e000) - malloc виділив 144 кб
29305: anon page @ 0804a004 (W) - тут malloc заносить мітки в початок і кінець виділеного регіону
29305: anon page @ 0804d00c (W)
29305: fsync (1)
29305: anon page @ 0804b000 (R) - збої при зверненні до сторінок з циклу
29305: anon page @ 0804c000 (R)
29305: fsync (2)
29305: brk (0806b000) - free повертає частину пам'яті
29305: brk -> 134 656 000 (0806b000)
29305: fsync (0) - `подальші цикли вже не виділяють пам'яті
29305: fsync (1)
29305: fsync (2)
У наведеному прикладі видно, що при виділенні 12К пам'яті malloc () виділяє спочатку набагато більший обсяг (144К), однак реально ці сторінки з фізичної пам'яті не виделяеются, і при зверненнях до них відбуваються сторінкові відмови. У першу і останню сторінку виділеної секції malloc заносить свої мітки (збій відбувається на операції запису).
2) Виділяємо поспіль блоки по 4 сторінки (не звільняючи), звертаючись до всіх сторінок:
21049: brk (00000000)
21049: brk -> (0804a000)
21049: brk (0806e000)
21049: brk -> (0806e000) - виділення першого блоку, malloc виділяє 144 кб
21049: anon page @ 0804a004 (W)
21049: anon page @ 0804d00c (W) - запис міток
21049: fsync (1)
21049: anon page @ 0804b000 (R)
21049: anon page @ 0804c000 (R) - звернення до виділеної області
21049: fsync (2)
21049: fsync (0)
21049: anon page @ 08050014 (W) - виділення наступних 12 кб (запис мітки)
21049: fsync (1)
21049: anon page @ 0804e000 (R)
21049: anon page @ 0804f000 (R) - звернення до виділеної області
21049: fsync (2)
...
21049: brk (0808f000) - черговий виклик malloc (), розширюємо сегмент даних
21049: brk -> 134 803 456 (0808f000)
21049: anon page @ 0806e064 (W)
21049: fsync (1)
21049: fsync -> -22 (ffffffea)
21049: anon page @ 0806c000 (R)
21049: anon page @ 0806d000 (R)
...
Таким чином, видно, що malloc виділяє пам'ять блоками по 128 з невеликим кб за допомогою виклику brk (). Розглянемо, що відбувається при збільшенні розміру запиту.
3) Запит послідовно 4, 8, 12, 16К, і т.д., звертаючись в циклі до всіх сторінок. При цьому, як тільки розмір виділення перевищує 128К, malloc виділяє пам'ять вже не з області сегмента даних програми (розширюваного визововм brk ()), а за допомогою mmap (), після чого free () його звільняє наступним munmap ():
789: mmap (00000000, 139264, rw-, PRIVATE | ANON, fd -1, @ f019a000)
789: mmap -> -1210519552 (b7d8f000)
789: anon page @ b7d8f004 (W)
789: fsync (1)
789: anon page @ b7d90000 (R)
...
789: anon page @ b7db0000 (R)
789: fsync (2)
789: munmap (b7d8f000, 139264)
789: munmap -> 0 (00000000)
При цьому виділяється трохи прийнятніший пам'яті, ніж було запропоновано, і кожного разу вона вся звільняється (тобто такі запити знову приведуть до виділенням). Знову-таки, сторінки спочатку повертаються невиділеними.
3) Третій приклад запитує пам'ять куди великими блоками, що збільшуються по 100 Мб. При відключеному overcommit'e пам'ять швидко закінчується, і черговий mmap повертає ENOMEM:
1536: mmap (00000000, 629149696, rw-, PRIVATE | ANON)
1536: mmap -> -12 (fffffff4)
1536: anon page @ b7e06de0 (R)
1536: brk (00000000)
1536: brk -> 134 520 832 (0804a000)
1536: brk (2d86b000)
1536: brk -> 134 520 832 (0804a000)
1536: mmap (00000000, 629280768, rw-, PRIVATE | ANON)
1536: mmap -> -12 (fffffff4)
1536: mmap (00000000, 2097152, rw-, PRIVATE | ANON)
1536: mmap -> -1212555264 (b7b9e000)
1536: munmap (b7b9e000, 401408)
1536: munmap -> 0 (00000000)
1536: munmap (b7d00000, 647168)
1536: munmap -> 0 (00000000)
1536: anon page @ b7c00008 (W)
1536: mmap (00000000, 629149696, rw-, PRIVATE | ANON)
1536: mmap -> -12 (fffffff4)
Бібліотека libc при цьому намагається спочатку виділити ту ж пам'ять за допомогою виклику brk (), потім знову за допомогою виклику mmap (), але вже дещо менший обсяг, проте і ці виклики проходять невдало.
4) Включимо overcommit. Тепер, поки програма не звертається до всіх сторінок з виділеного регіону, а лише до деяких (не витрачаючи при цьому всю фізичну пам'ять), виконання проходить нормально, і виклик mmap () успішно виділяє блоки пам'яті, значно які перевищують обсяг вільної оперативної пам'яті (близько 600 M):
2515: mmap (00000000, 996151296, rw-, PRIVATE | ANON)
2515: mmap -> 2089299968 (7c883000)
2515: anon page @ 7c883004 (W)
2515: fsync (1)
2515: anon page @ 8b603008 (R)
2515: anon page @ 9a383008 (R)
2515: anon page @ a9103008 (R)
2515: fsync (2)
2515: munmap (7c883000, 996151296)
2515: munmap -> 0 (00000000)
У даному прикладі виклик mmap () виділяє 900м, з яких ми звертаємося лише до чотирьох сторінок.
5) При спробі спробувати звернутися до всіх сторінок з виділеного регіону, дуже скоро пам'ять вичерпується і виникає ситуація, звана OOM - Out Of Memory. У цьому випадку ядро викликає функцію oom_kill (), яка вибирає процес-жертву для знищення, грунтуючись на витратах пам'яті, часу роботи, пріоритет і т.п., і вбиває обраний процес, з метою звільнити пам'ять. При цьому в журнал повідомлень налаштування ядра видається повідомлення про те, що спрацював oom_kill:
kernel: kwin invoked oom-killer: gfp_mask = 0x201d2, order = 0, oomkillad
6) Включимо тепер файл підкачки. У випадку, якщо обсяг виділяється регіону перевищує розмір доступної фізичної пам'яті, але менше сумарного розміру її і файлу підкачки, сторінки з регіону спочатку виділяються по мірі звернення до них, потім старі починають вивантажуватись у swap-файл, після чого на другому циклі зчитування відбувається їх підкачка звідти:
19225: anon page @ b7418bb0 (R)
...
19225: anon page @ b7602893 (R)
19225: swapfile page @ 0ae1507c (R)
19225: swapfile page @ 0d8cb0e6 (R)
...
19225: swapfile page @ 0af146b0 (R)
7) Системні виклики mlock () / mlockall () роблять невивантажуваного певну сторінку віртуальної пам'яті процесу або всі його сторінки - поточні і / або виділені в майбутньому, в залежності від переданих у виклик mlockall () прапорів.
Зробимо на початку виконання програми невигружемимі всі сторінки, що виділяються в майбутньому, після чого виділяємо блоки по 12 кб (не звільняючи):
13749: brk (00000000)
13749: brk -> 134 520 832 (0804a000)
13749: brk (0806e000)
13749: anon page @ 0804a000 (W)
...
13749: anon page @ 0806c000 (W)
13749: anon page @ 0806d000 (W)
13749: brk -> 134 668 288 (0806e000)
...
У даному випадку ядро ​​відразу після виділення віртуальної пам'яті звертається до всіх її сторінках з метою їх завантаження у фізичну пам'ять.
Таким чином, можна зробити наступні висновки:
1. При виділенні пам'яті ядро не виділяє відразу всі фізичні сторінки (якщо в mmap () не був переданий прапор MAP POPULATE або сторінки процесу не були заблоковані у фізичній пам'яті викликами mlock [all] () - в цих випадках сторінки всього регіону підвантажуються відразу)
2. Для виділення невеликих обсягів пам'яті бібліотека libc розширює сегмент даних програми викликом brk (), для запитів, больших 128К - використовує mmap ().

5. Overcommit є досить потужним засобом, що дозволяє виділяти набагато більше віртуальної пам'яті, ніж є на самому ділі, за умови використання лише доступного її обсягу (дана можливість може бути корисна для різних науково-інженерних пріоложеній). Однак, у випадку, якщо реально затребувана пам'ять перевищить суммарноий обсяг доступної і файлу підкачки, виникне критична ситуація браку пам'яті. Висновок

У рамках даної роботи було досліджено питання, пов'язані з розробкою драйверів під OS Linux, роботою ядра Linux з віртуальною пам'яттю і перехопленням системних викликів.
Драйвер може бути завантажений і вивантажений без перезавантаження системи. Він не впливає на роботу інших пристроїв і всієї системи в цілому, і не призводить до відчутних затримок в роботі.
Було проведено дослідження механізму виділення пам'яті в ядрі Linux і бібліотеці libс, досліджена технологія overcommit.
Можливість відстежити операції процесів в пам'яттю часто може бути дуже корисною при налагодженні програм, моніторингу або налаштування системи (наприклад, для підбору оптимальних параметрів сервера, який витрачає великий об'єм пам'яті).


Список використаної літератури

1. Jonathan Corbet. Linux Device Drivers, 3rd Edition.
2. Роберт Лав. Розробка ядра Linux, 2-е видання.
3. Peter Salzman. The Linux Kernel Module Programming Guide, 3rd Edition.
4. Клаудія Родрігес. Азбука ядра Linux.
5. Вихідні тексти ядра і документація до них.


Програми

Код драйвера

mmon.c

/ *
* Main module and entry point of memmon.
* /
# Include <linux/module.h>
# Include <linux/moduleparam.h>
# Include <linux/kernel.h>
# Include <linux/proc_fs.h>
# Include «common.h»
# Include «watch-pids.h»
# Include «mm-fault.h»
# Include «events.h»
# Include «syscalls.h»
/ *** Internal data *** /
/ *
* Procfs directory entry
* /
struct proc_dir_entry * procdir = NULL;
/ *
* Init entry point
* /
static int __init init (void)
{
int ret = - EBUSY;
procdir = proc_mkdir (PROCDIR, NULL);
if (! procdir)
goto out;
if (! init_watch_pids ())
goto out_procdir;
if (! init_events ())
goto out_watch_pids;
if (! capture_syscalls ())
goto out_events;
capture_mmfault ();
return 0;
out_events:
fini_events ();
out_watch_pids:
fini_watch_pids ();
out_procdir:
remove_proc_entry (PROCDIR, NULL);
out:
return ret;
}
module_init (init);
/ *
* Exit point
* /
static void __exit exit (void)
{
release_mmfault ();
restore_syscalls ();
fini_events ();
fini_watch_pids ();
remove_proc_entry (PROCDIR, NULL);
}
module_exit (exit);
/ *** Module info *** /
MODULE_LICENSE ("GPL");
MODULE_AUTHOR («Ivan Korotkov»);
MODULE_DESCRIPTION («Linux Virtual Memory Monitor»);

events.h

/ *
* Events ringbuffer.
* /
# Ifndef MEMMON_EVENTS_H
# Define MEMMON_EVENTS_H
/ * Filename in procfs directory * /
# Define EVENTS_ENTRY «events»
/ * Types of events * /
enum memmon_event_type
{
NOTUSED = 0, / * to divvent treating zero-filled region as event struct * /
MMAP2,
MUNMAP,
MREMAP,
MLOCK,
MUNLOCK,
MLOCKALL,
MUNLOCKALL,
BRK,
FSYNC,
ANON_PF,
SWAP_PF,
FILE_PF,
SYSCALLRET
};
/ *
* Struct describing each event
* /
struct memmon_event
{
/ * Type * /
enum memmon_event_type type;
/ * Caller PID * /
pid_t pid;
/ * Per-type data * /
union
{
struct
{
void __user * start;
size_t len;
} Munmap;
struct
{
void __user * start;
size_t len;
unsigned long prot, flags;
unsigned long fd, off;
} Mmap2;
struct
{
void __user * start [2];
size_t len ​​[2];
unsigned flags;
} Mremap;
struct
{
void __user * start;
size_t len;
} Mlock, munlock;
struct
{
unsigned long flags;
} Mlockall;
struct
{
void __user * addr;
} Brk;
struct
{
int fd;
} Fsync;
struct
{
void __user * addr;
int write;
} Pagefault;
struct
{
char * callname;
long ret;
} Callret;
};
};
# Define NEVENTS (EVENTS_BUFLEN / sizeof (struct memmon_event))
/ *
* Initializes event ringbuffer & creates / proc entry
* /
int init_events (void);
/ *
* Destroys ringbuffer & removes / proc entry
* /
void fini_events (void);
/ *
* Adds events to ringbuffer tail
* /
void put_event (const struct memmon_event * ev);
# Endif / / MEMMON_EVENTS_H

events.c

/ *
* Events ringbuffer.
* /
# Include <linux/module.h>
# Include <linux/moduleparam.h>
# Include <linux/kernel.h>
# Include <linux/seq_file.h>
# Include <linux/proc_fs.h>
# Include <linux/poll.h>
# Include <linux/mman.h>
# Include «common.h»
# Include «events.h»
/ *** Forward declarations *** /
static int events_open (struct inode * i, struct file * filp);
static unsigned events_poll (struct file * filp, struct poll_table_struct * pt);
static void * events_seqstart (struct seq_file * m, loff_t * pos);
static void events_seqstop (struct seq_file * m, void * p);
static void * events_seqnext (struct seq_file * m, void * p, loff_t * pos);
static int events_seqprint (struct seq_file * m, void * p);
/ * Default ringbuffer size * /
# Define EVENTS_BUFLEN (32 * 1024)
/ * Min ringbuffer size * /
# Define MIN_EVENTS_BUFLEN (8 * 1024)
/ *** Module parameters *** /
/ * Actual ringbuffer size * /
static int buflen = EVENTS_BUFLEN;
module_param (buflen, int, 0444);
/ *** File operations *** /
static const struct file_operations events_fops =
{
owner = THIS_MODULE,
open = events_open,
read = seq_read,
release = seq_release,
poll = events_poll
};
static const struct seq_operations events_seqop =
{
start = events_seqstart,
stop = events_seqstop,
next = events_seqnext,
show = events_seqprint
};
/ *** Internal data *** /
/ * Ringbuffer * /
static struct memmon_event * events;
/ * Last entry left in ringbuffer
* (Where 1st read should begin) * /
static int ev_start;
/ * Current write position * /
static int ev_end;
/ * Whether there was ringbuffer overflow * /
static int ev_ovf = 0;
DECLARE_WAIT_QUEUE_HEAD (ev_waitq);
spinlock_t ev_lock = SPIN_LOCK_UNLOCKED;
/ * Damn seq_file doesn't update file pos when we return NULL iterator,
* So we first return this one and then NULL on next seqnext () call * /
static void * dummy_ptr = & dummy_ptr;
/ *** Entry points *** /
/ *
* Open () handler
* /
static int events_open (struct inode * i, struct file * filp)
{
int ret;
/ *
* Ringbuffer is not seekable
* /
nonseekable_open (i, filp);
/ *
* Open seq_file and set its initial pos
* /
ret = seq_open (filp, & events_seqop);
if (! ret)
{
struct seq_file * m = filp-> private_data;
m-> private = filp;
m-> index = ev_start;
}
return ret;
}
/ *
* Poll / epoll () handler
* /
static unsigned events_poll (struct file * filp, struct poll_table_struct * pt)
{
struct seq_file * m = filp-> private_data;
unsigned mask = 0;
spin_lock (& ​​ev_lock);
poll_wait (filp, & ev_waitq, pt);
/ *
* The only poll event we can trigger is normal read event
* /
if (m-> index! = ev_end)
mask = POLLIN | POLLRDNORM;
spin_unlock (& ​​ev_lock);
return mask;
}
/ *
* Called by seq_file within read () request
* /
static void * events_seqstart (struct seq_file * m, loff_t * pos)
{
struct file * filp = m-> private;
spin_lock (& ​​ev_lock);
/ *
* Wait for data become available
* /
while (* pos == (loff_t) ev_end)
{
void * err = NULL;
/ * Can't schedule while atomic * /
spin_unlock (& ​​ev_lock);
if (filp-> f_flags & O_NONBLOCK)
err = ERR_PTR (-EAGAIN);
else if (wait_event_interruptible (ev_waitq, * pos! = (loff_t) ev_end))
err = ERR_PTR (-ERESTARTSYS);
/ *
* There IS a slim chance, that we loose waiting condition
* Between awakening and acquiring spinlock - hence while () loop
* /
spin_lock (& ​​ev_lock);
if (err)
return err;
}
return events + * pos;
}
/ *
* Finish read () request
* /
static void events_seqstop (struct seq_file * m, void * p)
{
spin_unlock (& ​​ev_lock);
}
/ *
* Iterate to next event
* /
static void * events_seqnext (struct seq_file * m, void * p, loff_t * pos)
{
struct memmon_event * ev;
/ * Dummy iterator - time to exit * /
if (p == dummy_ptr)
return NULL;
+ + * Pos;
ev = events + * pos;
/ * Overflow * /
if (ev - events> NEVENTS)
* Pos = 0;
/ *
* We reached end. Decrement file pos ('coz it will be incremented then back)
* And return dummy iterator (otherwise file pos won't be updated at all)
* /
if (* pos == (loff_t) ev_end)
{
-* Pos;
return dummy_ptr;
}
return events + * pos;
}
/ *
* Actually prints current iterator to read buffer
* /
static int events_seqprint (struct seq_file * m, void * p)
{
struct memmon_event * ev = p;
if (ev == dummy_ptr)
return 0;
seq_printf (m, "% d:», ev-> pid);
switch (ev-> type)
{
case MMAP2:
seq_printf (m, «mmap (% p,% u,», ev-> mmap2.start, ev-> mmap2.len);
if (ev-> mmap2.prot & PROT_READ)
seq_puts (m, «r»);
else
seq_puts (m, «-»);
if (ev-> mmap2.prot & PROT_WRITE)
seq_puts (m, «w»);
else
seq_puts (m, «-»);
if (ev-> mmap2.prot & PROT_EXEC)
seq_puts (m, «x,»);
else
seq_puts (m, «-,»);
if (ev-> mmap2.flags & MAP_SHARED)
seq_puts (m, «SHARED»);
else if (ev-> mmap2.flags & MAP_PRIVATE)
seq_puts (m, «PRIVATE»);
if (ev-> mmap2.flags & MAP_LOCKED)
seq_puts (m, «| LOCKED»);
if (ev-> mmap2.flags & MAP_ANON)
seq_puts (m, «| ANON»);
if (ev-> mmap2.flags & MAP_POPULATE)
seq_puts (m, «| READAHEAD»);
if (ev-> mmap2.flags & MAP_ANON)
seq_puts (m, ») \ n");
else
seq_printf (m, », fd% ld, @% p) \ n», (long) ev-> mmap2.fd,
(Void *) ev-> mmap2.off);
break;
case MUNMAP:
seq_printf (m, «munmap (% p,% d) \ n», ev-> munmap.start, ev-> munmap.len);
break;
case MREMAP:
seq_printf (m, «mremap (% p,% d ->% p,% d) \ n», ev-> mremap.start [0], ev-> mremap.len [0],
ev-> mremap.start [1], ev-> mremap.len [1]);
break;
case MLOCK:
seq_printf (m, «mlock (% p,% d) \ n», ev-> mlock.start, ev-> mlock.len);
break;
case MUNLOCK:
seq_printf (m, «munlock (% p,% d) \ n», ev-> munlock.start, ev-> munlock.len);
break;
case MLOCKALL:
seq_puts (m, «mlockall (»);
if (ev-> mlockall.flags & MCL_CURRENT)
{
seq_puts (m, «CURRENT»);
if (ev-> mlockall.flags & MCL_FUTURE)
seq_puts (m, «| FUTURE»);
}
else if (ev-> mlockall.flags & MCL_FUTURE)
seq_puts (m, «FUTURE»);
seq_puts (m, ») \ n");
break;
case MUNLOCKALL:
seq_puts (m, «munlockall () \ n");
break;
case BRK:
seq_printf (m, «brk (% p) \ n», ev-> brk.addr);
break;
case FSYNC:
seq_printf (m, «fsync (% d) \ n», ev-> fsync.fd);
break;
case ANON_PF:
seq_printf (m, «anon page @% p (% s) \ n», ev-> pagefault.addr,
ev-> pagefault.write? «W»: «R»);
break;
case SWAP_PF:
seq_printf (m, «swapfile page @% p (% s) \ n», ev-> pagefault.addr,
ev-> pagefault.write? «W»: «R»);
break;
case FILE_PF:
seq_printf (m, «shared file page @% p (% s) \ n», ev-> pagefault.addr,
ev-> pagefault.write? «W»: «R»);
break;
case SYSCALLRET:
seq_printf (m, "% s ->% ld (% p) \ n», ev-> callret.callname, ev-> callret.ret,
(Void *) ev-> callret.ret);
break;
default:
printk («memmon: Unexpected event% d \ n», ev-> type);
return 1;
}
return 0;
}
/ *** Exported entries *** /
/ *
* Initializes event ringbuffer & creates / proc entry
* /
int init_events (void)
{
struct proc_dir_entry * entry;
buflen = max (buflen, MIN_EVENTS_BUFLEN);
events = kzalloc (buflen, GFP_KERNEL);
if (! events)
{
printk («memmon: Event ringbuffer too big! \ n");
return 0;
}
ev_start = ev_end = 0;
entry = create_proc_entry (EVENTS_ENTRY, 0444, procdir);
if (entry)
entry-> proc_fops = & events_fops;
else
{
kfree (events);
return 0;
}
return 1;
}
/ *
* Destroys ringbuffer & removes / proc entry
* /
void fini_events (void)
{
remove_proc_entry (EVENTS_ENTRY, procdir);
kfree (events);
}
/ *
* Adds events to ringbuffer tail
* /
void put_event (const struct memmon_event * ev)
{
spin_lock (& ​​ev_lock);
events [ev_end] = * ev;
/ * Overflow * /
if (+ + ev_end> NEVENTS)
{
ev_start = ev_end = 0;
ev_ovf = 1;
}
/ *
* If overflow happened at least once, ev_start must be next to ev_end.
* Otherwise, it remains zero.
* /
if (ev_ovf & & + + ev_start> NEVENTS)
ev_start = 0;
spin_unlock (& ​​ev_lock);
wake_up_interruptible_sync (& ev_waitq);
}

watch-pids.h

/ *
* Selection of PIDs to watch for.
* /
# Ifndef MEMMON_WATCH_PIDS_H
# Define MEMMON_WATCH_PIDS_H
/ *
* Checks whether PID @ pid is divsent in PID set
* Returns 1 if divsent
* /
int pid_divsent (pid_t pid);
/ *
* Initializes PID set & creates / proc entry
* /
int init_watch_pids (void);
/ *
* Destroys PID set & removes / proc entry
* /
void fini_watch_pids (void);
# Endif / / MEMMON_WATCH_PIDS_H

watch-pids.c

/ *
* Selection of PIDs to watch for.
* /
# Include <linux/module.h>
# Include <linux/moduleparam.h>
# Include <linux/kernel.h>
# Include <linux/proc_fs.h>
# Include <linux/bitmap.h>
# Include <asm/uaccess.h>
# Include <asm/bitops.h>
# Include «common.h»
# Include «watch-pids.h»
/ *** Forward declarations *** /
static int watch_pids_open (struct inode * i, struct file * filp);
static int watch_pids_release (struct inode * i, struct file * filp);
static ssize_t watch_pids_read (struct file * filp, char __user * buf, size_t count, loff_t * off);
static ssize_t watch_pids_write (struct file * filp, const char __user * buf,
size_t count, loff_t * offp);
/ *** Internal data *** /
/ * Filename in procfs directory * /
# Define WATCHPID_ENTRY «watch-pids»
# Define PID_COUNT PID_MAX_DEFAULT + 1
/ * PIDs are stored in one single bitmap for 8192 entries
* This is VERY RARELY unacceptable * /
static DECLARE_BITMAP (watched_pids, PID_COUNT);
/ *** File operations *** /
static const struct file_operations watch_pids_fops =
{
owner = THIS_MODULE,
open = watch_pids_open,
read = watch_pids_read,
write = watch_pids_write,
release = watch_pids_release
};
/ *** Entry points *** /
/ *
* Open () handler
* /
static int watch_pids_open (struct inode * i, struct file * filp)
{
try_module_get (THIS_MODULE);
/ *
* If file opened for read, print PID set to internal buffer
* /
if (filp-> f_mode & FMODE_READ)
{
const int FDATA_SIZ = 32 * 1024;
char * fdata;
int len;
/ *
* Disallow mixed RW-access
* /
if (filp-> f_mode & FMODE_WRITE)
return - EINVAL;
fdata = kzalloc (FDATA_SIZ, GFP_KERNEL);
len = bitmap_scnlistprintf (fdata, FDATA_SIZ - 1,
watched_pids, PID_COUNT);
/ * Append \ n * /
if (len)
{
fdata [len + +] = '\ n';
fdata [len] = 0;
}
filp-> private_data = fdata;
}
return 0;
}
/ *
* Close () handler
* /
static int watch_pids_release (struct inode * i, struct file * filp)
{
module_put (THIS_MODULE);
if (filp-> private_data)
kfree (filp-> private_data);
return 0;
}
/ *
* Read () handler - simply return chunk of data from
* Divviously allocated and formatted buffer
* /
static ssize_t watch_pids_read (struct file * filp, char __user * buf,
size_t count, loff_t * offp)
{
size_t len ​​= strlen (filp-> private_data);
char * fdata = filp-> private_data;
if (* offp> = len)
return 0;
len = min (count, len - (size_t) (* offp));
if (copy_to_user (buf, fdata + (* offp), len))
return - EFAULT;
* Offp + = len;
return len;
}
/ *
* Write () handler
* Buffer must hold ASCII redivsentation of single integer
* If positive, it's value is PID to add to set
* If negative, it's absolute value is PID to remove from set
* If zero, PID set is cleared
* /
static ssize_t watch_pids_write (struct file * filp, const char __user * buf,
size_t count, loff_t * offp)
{
const size_t maxlen = 4096;
size_t len;
pid_t new_pid;
char * data;
ssize_t res = - ENOMEM;
/ * Copy up to one page to our buffer * /
len = min (maxlen, count);
data = kzalloc (len, GFP_KERNEL);
if (unlikely (! data))
return - ENOMEM;
if (copy_from_user (data, buf, len))
res = - EFAULT;
else if ((sscanf (data, '% d », & new_pid) == 1) & &
new_pid <= PID_COUNT & & new_pid> = - PID_COUNT)
{
if (new_pid> 0)
set_bit (new_pid, watched_pids);
else if (new_pid <0)
clear_bit (-new_pid, watched_pids);
else
bitmap_zero (watched_pids, PID_COUNT);
res = len;
}
else
/ * Buffer doesn't redivsent a number in PID range * /
res = - EIO;
kfree (data);
return res;
}
/ *** Exported entries *** /
/ *
* Checks whether PID @ pid is divsent in PID set
* Returns 1 if divsent
* /
int pid_divsent (pid_t pid)
{
if (pid> PID_COUNT | | pid <= 0)
return 0;
return test_bit (pid, watched_pids)? 1: 0;
}
/ *
* Initializes PID set & creates / proc entry
* /
int init_watch_pids (void)
{
struct proc_dir_entry * entry;
entry = create_proc_entry (WATCHPID_ENTRY, 0666, procdir);
if (entry)
entry-> proc_fops = & watch_pids_fops;
else
return 0;
bitmap_zero (watched_pids, PID_COUNT);
return 1;
}
/ *
* Destroys PID set & removes / proc entry
* /
void fini_watch_pids (void)
{
remove_proc_entry (WATCHPID_ENTRY, procdir);
}

syscalls.h

/ *
* Syscall capture facility.
* /
# Ifndef MEMMON_SYSCALLS_H
# Define MEMMON_SYSCALLS_H
/ *
* Installs handlers.
* /
int capture_syscalls (void);
/ *
* Uninstalls handlers
* /
void restore_syscalls (void);
# Endif / / MEMMON_SYSCALLS_H

syscalls.c

/ *
* Syscall capture facility.
* /
# Include <linux/module.h>
# Include <linux/moduleparam.h>
# Include <linux/kernel.h>
# Include <linux/proc_fs.h>
# Include «common.h»
# Include «syscalls.h»
# Include «events.h»
# Include «watch-pids.h»
/ *** Syscalls *** /
/ *
* They just put an appropriate event into ringbuffer
* /
asmlinkage void sys2_mmap2 (void __user * start, size_t length,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
struct memmon_event ev = {. type = MMAP2.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.mmap2.start = start;
ev.mmap2.len = length;
ev.mmap2.prot = prot>> 3;
ev.mmap2.flags = flags;
ev.mmap2.fd = fd;
ev.mmap2.off = pgoff;
put_event (& ev);
}
asmlinkage void sys2_mmap2_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «mmap»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_munmap (void __user * start, size_t length)
{
struct memmon_event ev = {. type = MUNMAP.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.munmap.start = start;
ev.munmap.len = length;
put_event (& ev);
}
asmlinkage void sys2_munmap_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «munmap»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_mremap (void __user * addr1, size_t length1,
unsigned long length2, unsigned long flags,
void __user * addr2)
{
struct memmon_event ev = {. type = MREMAP.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.mremap.start [0] = addr1;
ev.mremap.start [1] = addr2;
ev.mremap.len [0] = length1;
ev.mremap.len [1] = length2;
ev.mremap.flags = flags;
put_event (& ev);
}
asmlinkage void sys2_mremap_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «mremap»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_mlock (void __user * start, size_t length)
{
struct memmon_event ev = {. type = MLOCK.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.mlock.start = start;
ev.mlock.len = length;
put_event (& ev);
}
asmlinkage void sys2_mlock_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «mlock»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_munlock (void __user * start, size_t length)
{
struct memmon_event ev = {. type = MUNLOCK.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.munlock.start = start;
ev.munlock.len = length;
put_event (& ev);
}
asmlinkage void sys2_munlock_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «munlock»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_mlockall (unsigned long flags)
{
struct memmon_event ev = {. type = MLOCKALL.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.mlockall.flags = flags;
put_event (& ev);
}
asmlinkage void sys2_mlockall_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «mlockall»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_munlockall ()
{
struct memmon_event ev = {. type = MUNLOCKALL.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
put_event (& ev);
}
asmlinkage void sys2_munlockall_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «munlockall»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_brk (void __user * start)
{
struct memmon_event ev = {. type = BRK.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.brk.addr = start;
put_event (& ev);
}
asmlinkage void sys2_brk_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «brk»;
ev.callret.ret = ret;
put_event (& ev);
}
asmlinkage void sys2_fsync (int fd)
{
struct memmon_event ev = {. type = FSYNC.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.fsync.fd = fd;
put_event (& ev);
}
asmlinkage void sys2_fsync_exit (long ret)
{
struct memmon_event ev = {. type = SYSCALLRET.pid = current-> pid};
if (! pid_divsent (ev.pid)) return;
ev.callret.callname = «fsync»;
ev.callret.ret = ret;
put_event (& ev);
}
/ *** Handler tables *** /
/ * Kernel syscall table * /
extern void * sys_call_table [];
/ * Our table w / saved offsets * /
void * old_sys_call [NR_syscalls];
/ * Our div-call handlers * /
void * sys_call_trap [NR_syscalls];
/ * Our post-call handlers * /
void * sys_call_exit [NR_syscalls];
/ *
* Struct describind our handler
* /
struct syscall_handler
{
/ * Syscall nr * /
int nr;
/ * Pre-call & post-call handler * /
void * hand1, * hand2;
};
# Define SYSCALL_HANDLER (name) {__NR_ # # name, sys2_ # # name, sys2_ # # name # # _exit}
# Define SYSCALL_HANDLERS_END () {0, 0, 0}
/ *
* Main handler table
* Each SYSCALL_HANDLER (name) entry installs handlers
* «Sys2_name/sys2_name_exit for sys_name call.
* /
struct syscall_handler syscalls [] =
{
SYSCALL_HANDLER (mmap2),
SYSCALL_HANDLER (munmap),
SYSCALL_HANDLER (mremap),
SYSCALL_HANDLER (mlock),
SYSCALL_HANDLER (munlock),
SYSCALL_HANDLER (mlockall),
SYSCALL_HANDLER (munlockall),
SYSCALL_HANDLER (brk),
SYSCALL_HANDLER (fsync),
SYSCALL_HANDLERS_END ()
};
/ * Located in syscall-entry.S * /
void syscalls_entry (void);
/ *** Exported entries *** /
/ *
* Installs handlers.
* /
int capture_syscalls (void)
{
int i;
for (i = 0; syscalls [i]. hand1; + + i)
{
int nr = syscalls [i]. nr;
sys_call_trap [nr] = syscalls [i]. hand1;
sys_call_exit [nr] = syscalls [i]. hand2;
old_sys_call [nr] = sys_call_table [nr];
sys_call_table [nr] = syscalls_entry;
}
return 1;
}
/ *
* Uninstalls handlers
* /
void restore_syscalls (void)
{
int i;
for (i = 0; syscalls [i]. hand1; + + i)
{
int nr = syscalls [i]. nr;
sys_call_table [nr] = old_sys_call [nr];
}
}

syscalls-entry.S

/ *
* Syscall entry / exit capture
* /
# Include «offsets.h»
/ * Entry handler table * /
extern sys_call_trap
/ * Exit handler table * /
extern sys_call_exit
/ * Global entry for our syscalls * /
syscalls_entry:
/ * Save registers in order syscall handlers expect 'em * /
pushl% eax
pushl% ebp
pushl% edi
pushl% esi
pushl% edx
pushl% ecx
pushl% ebx
/ * Save eax * /
movl% eax, TI_stk0 (% ebp)
/ * Call our handler * /
call * sys_call_trap (,% eax, 4)
/ * Fake return address * /
movl 28 (% esp),% eax
movl% eax, TI_stk0 + 4 (% ebp)
movl $ sysreturn, 28 (% esp)
/ * Restore context * /
popl% ebx
popl% ecx
popl% edx
popl% esi
popl% edi
popl% ebp
popl% eax
/ * Jump to default system handler * /
jmpl * old_sys_call (,% eax, 4)
sysreturn:
/ * Save registers * /
pushal
/ * Pass new% eax to exit handler * /
pushl% eax
/ * Restore original% eax * /
movl TI_stk0 (% ebp),% eax
/ * Call our exit handler * /
call * sys_call_exit (,% eax, 4)
/ * Restore context * /
popl% eax
popal
/ * Jump back to syscall dispatcher entry * /
jmpl * TI_stk0 + 4 (% ebp)
globl syscalls_entry

gen-offsets.c

/ /
# Define __KERNEL__
/ * Bugoga * /
# Include <linux/kernel.h>
# Include <linux/autoconf.h>
# Include <linux/thread_info.h>
# Include <stdio.h>
int main ()
{
printf («# define TI_stk0% d \ n», offsetof (struct thread_info, supervisor_stack));
return 0;
}

mm-fault.h

/ *
* Pagefault interception.
* /
# Ifndef MEMMON_MM_FAULT_H
# Define MEMMON_MM_FAULT_H
/ *
* Install pagefault handler
* /
void capture_mmfault (void);
/ *
* Uninstall handler
* /
void release_mmfault (void);
# Endif / / MEMMON_MM_FAULT_H

mm-fault.c

/ *
* Pagefault interception.
* /
# Include <linux/module.h>
# Include <linux/moduleparam.h>
# Include <linux/kernel.h>
# Include <linux/mm.h>
# Include «common.h»
# Include «mm-fault.h»
# Include «events.h»
# Include «watch-pids.h»
/ *
* Dirty kernel hack: PF hook that is called every time
* Some process PF's for some page that BELONGS to his VMA space.
* /
extern void (* mm_handle_fault_hook) (struct mm_struct * mm, struct vm_area_struct * vma,
void __user * address, pte_t * pte,
pmd_t * pmd, int write_access);
/ *
* Pagefault handler
* /
void mm_handle_fault (struct mm_struct * mm, struct vm_area_struct * vma,
void __user * address, pte_t * pte,
pmd_t * pmd, int write_access)
{
struct memmon_event ev = {. pid = current-> pid};
pte_t entry = * pte;
/ *
* If PF happened due to R / W or U / S access violation, ignore it
* /
if (! pid_divsent (current-> pid) | | pte_divsent (entry))
return;
/ *
* Faulted page is either backed by swapfile, some shared executable file
* Or no file yet at all (anonymous page)
* /
if (pte_none (entry))
ev.type = ANON_PF;
else if (pte_file (entry))
ev.type = FILE_PF;
else
ev.type = SWAP_PF;
ev.pagefault.addr = address;
ev.pagefault.write = write_access;
put_event (& ev);
}
/ *** Exported entries *** /
/ *
* Install pagefault handler
* /
void capture_mmfault (void)
{
mm_handle_fault_hook = mm_handle_fault;
}
/ *
* Uninstall handler
* /
void release_mmfault (void)
{
mm_handle_fault_hook = NULL;
}

common.h

/ *
* Common defines and global data
* /
# Ifndef MEMMON_COMMON_H
# Define MEMMON_COMMON_H
/ * Procfs directory name * /
# Define PROCDIR «memmon»
/ *
* Procfs directory entry
* /
extern struct proc_dir_entry * procdir;
# Endif / / MEMMON_COMMON_H

Makefile

#
ifneq ($ (KERNELRELEASE),)
obj-m: = memmon.o
memmon-objs: = mmon.o events.o watch-pids.o syscalls.o syscalls-entry.o mm-fault.o
else
KERNELDIR? = / Lib / modules / $ (shell uname - r) / build
PWD: = $ (shell pwd)
all: offsets.h modules
offsets.h: $ (KERNELDIR) / include / asm / thread_info.h
$ (MAKE) gen-offsets
gen-offsets> offsets.h
$ (RM) gen-offsets
clean modules:
$ (MAKE) - C $ (KERNELDIR) M = $ (PWD) $ (MAKECMDGOALS)
PHONY: modules.DEFAULT all
endif

Іспрвленіе для для ядра (2.6.20.1)

diff - arNC 3 linux-2.6.20.1-j/kernel/kallsyms.c linux-2.6.20.1-a/kernel/kallsyms.c
*** Linux-2.6.20.1-j/kernel/kallsyms.c 2007-02-20 09:34:32.000000000 +0300
- Linux-2.6.20.1-a/kernel/kallsyms.c 2007-05-26 22:27:23.000000000 +0400
***************
*** 452,454 ****
- 452,460 -
__initcall (kallsyms_init);
EXPORT_SYMBOL (__print_symbol);
+
+ / * HACK * /
+
+ Extern void * sys_call_table [];
+
+ EXPORT_SYMBOL_GPL (sys_call_table);
diff - arNC 3 linux-2.6.20.1-j/mm/memory.c linux-2.6.20.1-a/mm/memory.c
*** Linux-2.6.20.1-j/mm/memory.c 2007-02-20 09:34:32.000000000 +0300
- Linux-2.6.20.1-a/mm/memory.c 2007-05-28 22:08:41.000000000 +0400
***************
*** 2369,2374 ****
- 2378,2390 -
return VM_FAULT_MAJOR;
}
+ / * DIRTY HACK * /
+ Void (* mm_handle_fault_hook) (struct mm_struct * mm,
+ Struct vm_area_struct * vma, unsigned long address,
+ Pte_t * pte, pmd_t * pmd, int write_access) = NULL;
+
+ EXPORT_SYMBOL_GPL (mm_handle_fault_hook);
+
/ *
* These routines also need to handle stuff like marking pages dirty
* And / or accessed for architectures that don't do it in hardware (most
***************
*** 2390,2395 ****
- 2406,2414 -
pte_t old_entry;
spinlock_t * ptl;
+ If (mm_handle_fault_hook)
+ Mm_handle_fault_hook (mm, vma, address, pte, pmd, write_access);
+
old_entry = entry = * pte;
if (! pte_divsent (entry)) {
if (pte_none (entry)) {
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Курсова
104.5кб. | скачати


Схожі роботи:
Моніторинг системних викликів створення обігу та видалення сегментів пам`яті, що розділяється в ОС Linux
Організація пам`яті СП Доступ до пам`яті Блоки пам`яті
Характеристики процесора та внутрішньої пам`яті комп`ютера швидкодію розрядність обсяг пам`яті
Види пам`яті витісняють статичну пам`ять
Пам`ять і закони пам`яті
Види пам яті
Фізіологія пам`яті
Розвиток пам яті 3
Організація пам яті МП IA 32
© Усі права захищені
написати до нас